﻿/*
 * Created by SharpDevelop.
 * User: sanmadmin
 * Date: 7/6/2011
 * Time: 2:59 AM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Diagnostics;

namespace RTMP
{
    /// <summary>
    /// Description of NetStream.
    /// </summary>
    public class NetStream
    {
        NetConnection con;
        public string name = "";
        internal int ID = -1;
        public bool live = true;
        private Logger logger = new Logger();
        public delegate void StatusEventHandler(object param);
        public event StatusEventHandler Status;
        internal ulong videoStartTimeStamp = 0;
        internal ulong audioStartTimeStamp = 0;
        internal ulong saveStartTime = 0;
        public bool SavePaused = false;
        private UInt64 VideoStartTime = 0, VideoEndTime = 0, AudioStartTime = 0, AudioEndTime = 0, SaveTime = 0, SavePauseTime = 0;
        byte[] flvHeader = new byte[] 
        { 
          Convert.ToByte('F'), Convert.ToByte('L'), Convert.ToByte('V'), 0x01,
          0x05, // video + audio, we finalize later if the value is different
          0x00, 0x00, 0x00, 0x09,
          0x00, 0x00, 0x00, 0x00 // first prevTagSize=0
        };
        public Stream outputStream = null;
        internal delegate void updateHandler(Tag t);

        internal updateHandler updater = null;

        public NetStream(NetConnection con)
        {
            this.con = con;
        }
        ~NetStream()
        {
            try { close(); }
            catch { }
        }
        public void close()
        {
            if (outputStream != null)
                StopSave();
            if (con != null && con.NSList.ContainsKey(ID))
            {
                con.SendStop(this);
                con.NSList.Remove(ID);
            }
        }
        public void PauseSave()
        {
            if (outputStream == null || saveStartTime == 0)
                return;
            SavePaused = true;
        }
        public void ContinueSave()
        {
            if (outputStream == null)
                return;
            SavePaused = false;
        }
        public void StopSave()
        {
            if (outputStream != null)
            {
                outputStream.Close();
                outputStream.Dispose();
                outputStream = null;
                saveStartTime = 0;
                SavePauseTime = 0;
                SaveTime = 0;
                SavePaused = false;
            }
        }
        public void StartSave(string path)
        {
            string dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(Path.GetDirectoryName(path)))
            {
                logger.Log(string.Format("Directory {0} not exists(StartSave)", Path.GetDirectoryName(path)));
                return;
            }
            try
            {
                outputStream = new FileStream(path, FileMode.OpenOrCreate);
                outputStream.Write(flvHeader, 0, flvHeader.Length);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
        public void Stop()
        {
            if (outputStream != null)
                StopSave();
            con.SendStop(this);
        }
        internal void handleEvents(string status, string code)
        {

        }
        internal void handleStatus(object param)
        {
            onStatus(param);
        }
        public bool play(string name)
        {
            try
            {
                if (con.NSPendings.ContainsKey(name))
                    return false;
                this.name = name;
                con.NSPendings.Add(name, this);
                con.SendCreateStream();

            }
            catch (Exception ex)
            {
                logger.Log(ex.Message);
            }
            return true;
        }
        internal void handlePacket(RTMPPacket packet)
        {
            uint prevTagSize = 0;

            // skip video info/command packets
            if (packet.PacketType == PacketType.Video && packet.m_nBodySize == 2 && ((packet.m_body[0] & 0xf0) == 0x50))
            {
                return;
            }

            if (packet.PacketType == PacketType.Video && packet.m_nBodySize <= 5)
            {
                logger.Log(string.Format("ignoring too small video packet: size: {0}", packet.m_nBodySize));
                return;
            }

            if (packet.PacketType == PacketType.Audio && packet.m_nBodySize <= 1)
            {
                logger.Log(string.Format("ignoring too small audio packet: size: {0}", packet.m_nBodySize));
                return;
            }

            // audio (0x08), video (0x09) or metadata (0x12) packets :
            // construct 11 byte header then add rtmp packet's data
            if (packet.PacketType == PacketType.Audio || packet.PacketType == PacketType.Video || packet.PacketType == PacketType.Metadata)
            {
                if (updater != null)
                {
                    Tag t = new Tag();
                    try
                    {
                        t.DataSize = packet.m_nBodySize;
                        t.TagType = (byte)packet.PacketType;
                        t.PreviousTagSize = 11 + packet.m_nBodySize;
                        if (packet.PacketType == PacketType.Video)
                        {
                            try
                            {
                                if (videoStartTimeStamp == 0)
                                {
                                    videoStartTimeStamp = packet.m_nTimeStamp;
                                    audioStartTimeStamp = packet.m_nTimeStamp;
                                    VideoStartTime = 0;
                                    VideoEndTime = (packet.m_nTimeStamp - videoStartTimeStamp) * 10000;
                                }
                                else
                                {
                                    VideoStartTime = VideoEndTime;
                                    VideoEndTime = (packet.m_nTimeStamp - videoStartTimeStamp) * 10000;
                                }
                                t.StartTime = VideoStartTime;
                                t.EndTime = VideoEndTime;
                                VideoTag vt = new VideoTag();
                                vt.FrameType = BitConverterBE.GetBytes((uint)packet.m_body[0] >> 4)[3];
                                vt.CodecID = BitConverterBE.GetBytes((uint)packet.m_body[0] & 0x0F)[3];
                                vt.data = packet.m_body;
                                t.data = (object)vt;
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine("Exception while prepering video data: " + ex.Message);
                            }
                        }
                        else if (packet.PacketType == PacketType.Audio)
                        {
                            try
                            {
                                // return true;
                                if (audioStartTimeStamp == 0)
                                {
                                    videoStartTimeStamp = packet.m_nTimeStamp;
                                    audioStartTimeStamp = packet.m_nTimeStamp;
                                    AudioStartTime = 0;
                                    AudioEndTime = (packet.m_nTimeStamp - audioStartTimeStamp) * 10000;
                                }
                                else
                                {
                                    AudioStartTime = AudioEndTime;
                                    AudioEndTime = (packet.m_nTimeStamp - audioStartTimeStamp) * 10000;
                                }
                                t.StartTime = AudioStartTime;
                                t.EndTime = AudioEndTime;
                                AudioTag at = new AudioTag();
                                at.SoundFormat = BitConverterBE.GetBytes((uint)packet.m_body[0] >> 4)[3];
                                at.SoundRate = BitConverterBE.GetBytes((uint)((packet.m_body[0] & 0x0C)) >> 2)[3];
                                at.SoundSize = BitConverterBE.GetBytes(((uint)(packet.m_body[0] & 0x02)) >> 1)[3];
                                at.SoundType = BitConverterBE.GetBytes((uint)(packet.m_body[0] & 0x01))[3];
                                at.data = packet.m_body;
                                t.data = (object)at;
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine("Exception while prepering audio data: " + ex.Message);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Exception while prepering updater data: " + ex.Message);
                        return;
                    }
                    updater(t);
                }
                if (outputStream == null)
                    return;
                if (SavePaused)
                {
                    SavePauseTime = ((ulong)packet.m_nTimeStamp) - saveStartTime;
                    return;
                }
                if (SavePauseTime != 0)
                {
                    saveStartTime += SavePauseTime;
                    SavePauseTime = 0;
                }
                if (saveStartTime == 0)
                {
                    SaveTime = 0;
                    saveStartTime = packet.m_nTimeStamp;
                }
                else
                    SaveTime = ((ulong)packet.m_nTimeStamp) - saveStartTime;
                //nTimeStamp = (ulong)packet.m_nTimeStamp;
                prevTagSize = 11 + packet.m_nBodySize;
                outputStream.WriteByte((byte)packet.PacketType);

                List<byte> somebytes = new List<byte>();
                Coding.EncodeInt24(somebytes, (int)packet.m_nBodySize);
                Coding.EncodeInt24(somebytes, (int)SaveTime);
                outputStream.Write(somebytes.ToArray(), 0, somebytes.Count);
                somebytes.Clear();
                outputStream.WriteByte((byte)(((SaveTime) & 0xFF000000) >> 24));
                // stream id
                Coding.EncodeInt24(somebytes, 0);
                outputStream.Write(somebytes.ToArray(), 0, somebytes.Count);
                somebytes.Clear();

                // body
                outputStream.Write(packet.m_body, 0, (int)packet.m_nBodySize);
                // prevTagSize
                Coding.EncodeInt32(somebytes, (int)prevTagSize);
                outputStream.Write(somebytes.ToArray(), 0, somebytes.Count);
                somebytes.Clear();
            }
            // correct tagSize and obtain timestamp if we have an FLV stream            
            /*else if (packet.PacketType == PacketType.FlvTags)
            {
                List<byte> data = packet.m_body.ToList();

                uint pos = 0;

                // grab first timestamp and see if it needs fixing 
                nTimeStamp = (uint)Coding.ReadInt24(packet.m_body, 4);
                nTimeStamp |= (uint)(packet.m_body[7] << 24);
                var delta = packet.m_nTimeStamp - nTimeStamp;

                while (pos + 11 < packet.m_nBodySize)
                {
                    uint dataSize = (uint)Coding.ReadInt24(packet.m_body, (int)pos + 1); // size without header (11) and without prevTagSize (4)
                    nTimeStamp = (uint)Coding.ReadInt24(packet.m_body, (int)pos + 4);
                    nTimeStamp |= (uint)(packet.m_body[pos + 7] << 24);

                    if (delta != 0)
                    {
                        nTimeStamp += delta;
                        List<byte> newTimeStampData = new List<byte>();
                        Coding.EncodeInt24(newTimeStampData, (int)nTimeStamp);
                        data[(int)pos + 4] = newTimeStampData[0];
                        data[(int)pos + 5] = newTimeStampData[1];
                        data[(int)pos + 6] = newTimeStampData[2];
                        data[(int)pos + 7] = (byte)(nTimeStamp >> 24);
                    }

                    if (pos + 11 + dataSize + 4 > packet.m_nBodySize)
                    {
                        if (pos + 11 + dataSize > packet.m_nBodySize)
                        {
                            logger.Log(string.Format("Wrong data size ({0}), stream corrupted, aborting!", dataSize));
                            return false;
                        }
                        // we have to append a last tagSize!
                        prevTagSize = dataSize + 11;
                        Coding.EncodeInt32(data, (int)prevTagSize);
                    }
                    else
                    {
                        prevTagSize = (uint)Coding.ReadInt32(packet.m_body, (int)(pos + 11 + dataSize));

                        if (prevTagSize != (dataSize + 11))
                        {
                            //Tag and data size are inconsistent, writing tag size according to dataSize+11
                            prevTagSize = dataSize + 11;
                            Coding.EncodeInt32(data, (int)prevTagSize, pos + 11 + dataSize);
                        }
                    }

                    pos += prevTagSize + 4;//(11+dataSize+4);
                }

                stream.Write(data.ToArray(), 0, data.Count);
            }*/
            return;
        }


        private void onStatus(object param)
        {
            if (Status != null)
                Status(param);
        }


    }
}
